package com.github.cyf.databaseLock.impl;

import com.github.cyf.databaseLock.AbstractSimpleLock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;

/**
 * @author chenyifan
 * @create 2024-07-12 9:13
 */
@Slf4j
public class RedisLock extends AbstractSimpleLock {

    /**
     * 释放锁脚本，
     * 先 redis.call('GET', KEYS[1]) == ARGV[1] 防止误删锁
     * del 会返回删除成功的个数
     */
    private final String RELEASE_SCRIPT = "return redis.call('GET', KEYS[1]) == ARGV[1] and redis.call('DEL', KEYS[1]) == 1";
    /**
     * 刷新锁脚本，expire 会返回设置成功的个数
     */
    private final String LOCK_REFRESH_SCRIPT =
            "if redis.call('GET', KEYS[1]) == ARGV[1] then " +
                    "    return redis.call('EXPIRE', KEYS[1], ARGV[2]) == 1 " +
                    "end " +
                    "return false ";

    private final RedisScript<Boolean> releaseScript = new DefaultRedisScript<>(RELEASE_SCRIPT, Boolean.class);
    private final RedisScript<Boolean> refreshScript = new DefaultRedisScript<>(LOCK_REFRESH_SCRIPT, Boolean.class);

    private final RedisTemplate redisTemplate;

    public RedisLock(ThreadPoolTaskScheduler taskScheduler, RedisTemplate redisTemplate) {
        super(taskScheduler);
        this.redisTemplate = redisTemplate;
    }

    @Override
    public boolean refreshLock(String key, String token) {
        Boolean result = (Boolean) redisTemplate.execute(refreshScript, Arrays.asList(key), token, LOCK_HOLD_TIME);
        return result;
    }

    @Override
    public String doAcquire(String key, String token) {
        Boolean result = redisTemplate.opsForValue().setIfAbsent(key, token, LOCK_HOLD_TIME, TimeUnit.SECONDS);
        return result ? token : null;
    }

    @Override
    public boolean doRelease(String key, String token) {
        Boolean result = (Boolean) redisTemplate.execute(releaseScript, Arrays.asList(key), token);
        return result;
    }
}
